/*
 * ConfiguredHost.java
 *
 * Created on November 2, 2007, 2:49 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.www;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import org.atomojo.www.util.IdentityFilter;
import org.atomojo.www.util.SecurityGuard;
import org.atomojo.www.util.Identity;
import org.atomojo.www.util.IdentityManager;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Comparator;
import java.util.Enumeration;
import javax.net.ssl.HttpsURLConnection;
import org.atomojo.app.client.Entry;
import org.atomojo.app.client.Feed;
import org.atomojo.app.client.FeedBuilder;
import org.atomojo.app.client.FeedClient;
import org.atomojo.app.client.Link;
import org.atomojo.app.client.LinkSet;
import org.atomojo.app.client.Ontology;
import org.atomojo.app.client.Term;
import org.atomojo.app.client.Text;
import org.atomojo.www.util.Base64Coder;
import org.atomojo.www.util.ProxyApplication;
import org.atomojo.www.util.ResourceManager;
import org.atomojo.www.util.URLRetriever;
import org.atomojo.www.util.script.ScriptManager;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Application;
import org.restlet.Client;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Protocol;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.log.AccessLogFileHandler;
import org.restlet.resource.Directory;
import org.restlet.routing.Router;
import org.restlet.routing.Template;
import org.restlet.routing.TemplateRoute;
import org.restlet.routing.VirtualHost;

/**
 *
 * @author alex
 */
public class ConfiguredHost
{
   static {
      try {
         Ontology.getDefaultInstance().load(ConfiguredHost.class.getResource("www.ontology.xml"));
      } catch (Exception ex) {
         ex.printStackTrace();
      }
   }

   final static URI T_BASE                           = URI.create("http://www.atomojo.org/O/www/configuration");
   
   final static URI T_APP                            = Ontology.getDefaultInstance().find(T_BASE,"application").getURI();
   final static URI T_APP_MATCH                      = Ontology.getDefaultInstance().find(T_BASE,"application/match").getURI();
   final static URI T_APP_MATCH_MODE                 = Ontology.getDefaultInstance().find(T_BASE,"application/match/mode").getURI();
   final static URI T_APP_PRIORITY                   = Ontology.getDefaultInstance().find(T_BASE,"application/priority").getURI();
   final static URI T_APP_CLASS                      = Ontology.getDefaultInstance().find(T_BASE,"application/class").getURI();
   final static URI T_APP_PROXY                      = Ontology.getDefaultInstance().find(T_BASE,"application/proxy").getURI();
   final static URI T_APP_RESOURCE                   = Ontology.getDefaultInstance().find(T_BASE,"application/resource").getURI();
   final static URI T_APP_PREFIX                     = Ontology.getDefaultInstance().find(T_BASE,"application/prefix").getURI();
   
   final static URI T_LAYOUT                         = Ontology.getDefaultInstance().find(T_BASE,"layout").getURI();
   final static URI T_LAYOUT_MATCH                   = Ontology.getDefaultInstance().find(T_BASE,"layout/match").getURI();
   final static URI T_LAYOUT_PRIORITY                = Ontology.getDefaultInstance().find(T_BASE,"layout/priority").getURI();
   final static URI T_LAYOUT_MEDIA_TYPE              = Ontology.getDefaultInstance().find(T_BASE,"layout/media-type").getURI();
   
   final static URI T_RESOURCE                       = Ontology.getDefaultInstance().find(T_BASE,"resource").getURI();
   final static URI T_RESOURCE_NAME                  = Ontology.getDefaultInstance().find(T_BASE,"resource/name").getURI();
   final static URI T_RESOURCE_PATH                  = Ontology.getDefaultInstance().find(T_BASE,"resource/path").getURI();
   final static URI T_RESOURCE_RELATION              = Ontology.getDefaultInstance().find(T_BASE,"resource/relation").getURI();
   final static URI T_RESOURCE_QUERY                 = Ontology.getDefaultInstance().find(T_BASE,"resource/query").getURI();
   final static URI T_RESOURCE_MEDIA_TYPE            = Ontology.getDefaultInstance().find(T_BASE,"resource/media-type").getURI();
   
   final static URI T_SECURITY_PROTECTED_PATH        = Ontology.getDefaultInstance().find(T_BASE,"security/protected/path").getURI();
   final static URI T_SECURITY_REDIRECT_UNAUTHORIZED = Ontology.getDefaultInstance().find(T_BASE,"security/redirect/unauthorized").getURI();
   final static URI T_SECURITY_REDIRECT_LOGIN        = Ontology.getDefaultInstance().find(T_BASE,"security/redirect/login").getURI();
   final static URI T_SECURITY_GROUP_ID              = Ontology.getDefaultInstance().find(T_BASE,"security/group/id").getURI();
   final static URI T_SECURITY_ROLE_ID               = Ontology.getDefaultInstance().find(T_BASE,"security/role/id").getURI();

   class MediaRetriever
   {

      URI location;
      String username;
      String password;
      /** Creates a new instance of MediaRetriever */
      public MediaRetriever(URI location,String username, String password)
      {
         this.location = location;
         this.username = username;
         this.password = password==null ? "" : password;
      }

      public Reader open() 
         throws IOException
      {
         URLConnection connection = location.toURL().openConnection();
         if (connection instanceof HttpsURLConnection) {
            HttpsURLConnection https = (HttpsURLConnection)connection;
            https.setHostnameVerifier(org.apache.commons.ssl.HostnameVerifier.DEFAULT_AND_LOCALHOST);
         }
         if (username!=null) {
            connection.setRequestProperty("Authorization", "Basic " + Base64Coder.encode(username+':'+password));
         }
         String charset = "UTF-8";
         InputStream is = connection.getInputStream();
         String contentType = connection.getContentType();
         if (contentType!=null) {
            int semicolon = contentType.indexOf(';');
            if (semicolon>=0) {
               String type = contentType.substring(0,semicolon);
               String rest = contentType.substring(semicolon+1);
               int start = rest.indexOf("charset=");
               if (start>=0) {
                  charset = rest.substring(start+8);
               }
               contentType = type;
            }
         }
         return new InputStreamReader(is,charset);
      }
   }
   class AppInfo {
      Application app;
      Date edited;
      String match;
      AppInfo(Application app,Date edited) {
         this.app = app;
         this.edited = edited;
         this.match = null;
      }
   }
   
   Context context;
   Client client;
   Router internalRouter;
   SecurityGuard security;
   VirtualHost vhost;
   Configuration.Host hostConf;
   Date edited;
   Link autoConf;
   Router router;
   DocumentLoader docLoader;
   Map<String,AppInfo> applications;
   List<AppInfo> staticApplications;
   ScriptManager scriptManager;
   ResourceManager resourceManager;
   Logger hostLog;
   boolean isStatic;
   
   /** Creates a new instance of ConfiguredHost */
   public ConfiguredHost(Context context,Router internalRouter,Configuration.Interface iface,Configuration.Host hostConf,Date edited,boolean isStatic)
   {
      this.context = context;
      this.internalRouter = internalRouter;
      this.hostConf = hostConf;
      this.edited = edited;
      this.docLoader = new SAXDocumentLoader();
      this.isStatic = isStatic;
      context.getAttributes().put(WebComponent.LINKS_ATTR, hostConf.getLinks());
      getLogger().info("Adding host "+hostConf.getName()+":"+iface.getPort());
      this.vhost = new VirtualHost(context) {
         public void handle(Request request, Response response) {
            long startTime = System.currentTimeMillis();
            super.handle(request,response);
            if (hostLog!=null) {
               int duration = (int) (System.currentTimeMillis() - startTime);
               hostLog.log(Level.INFO,formatLog(request,response,duration));
            }
         }
      };
      if (!iface.getAddress().equals("*")) {
         try {
            InetAddress addr = InetAddress.getByName(iface.getAddress());
            String saddr = addr.toString();
            saddr = saddr.substring(saddr.indexOf('/')+1);
            getLogger().info("Restricting "+hostConf.getName()+" to address "+saddr);
            vhost.setServerAddress(saddr);
         } catch (UnknownHostException ex) {
            getLogger().severe("Cannot resolve host name "+iface.getAddress());
         }
      }
      if (!hostConf.getName().equals("*")) {
         vhost.setHostDomain(hostConf.getName());
      }
      vhost.setHostPort(Integer.toString(iface.getPort()));
      if (hostConf.getLogConfiguration().get("pattern")!=null) {
         String name = hostConf.getName();
         if (name.equals("*")) {
            name = "any";
         }
         name = "host."+name;
         hostLog = Logger.getLogger(name);
         setupLog();
      }
      router = vhost;

      for (String name : hostConf.getParameters().keySet()) {
         String value = hostConf.getParameters().get(name);
         getLogger().info("Adding host parameter "+name+" -> "+value);
         context.getParameters().set(name,value,false);
      }

      List<Link> auths = hostConf.getLinks().get("auth-service");
      if (auths!=null && auths.size()>0) {
         Link authLink = auths.get(0);
         getLogger().info("Adding identity and security filters.");
         router = new Router(context);
         router.setDefaultMatchingMode(Template.MODE_STARTS_WITH);
         security = new SecurityGuard(context);
         security.setNext(router);
         final IdentityFilter filter = new IdentityFilter(context,security,new Reference(authLink.getLink().toString()));
         vhost.attachDefault(filter);
         this.context.getAttributes().put(IdentityManager.ATTR, new IdentityManager() {
            public void add(String id,Identity identity) {
               filter.addIdentity(id, identity);
            }
            public boolean remove(String id)
            {
               return filter.removeIdentity(id);
            }
         });
      }
      this.staticApplications = new ArrayList<AppInfo>();
      for (Configuration.Content content : hostConf.getContentSources()) {
         try {
            final String uri = content.getSource().toString();
            String scheme = content.getSource().getScheme();
            if (scheme.equals("http") || scheme.equals("https")) {
               getLogger().info("  Proxy: "+content.getSource()+" at "+content.getMatch());
               ProxyApplication proxy = new ProxyApplication(context,uri.toString());
               proxy.getTunnelService().setEnabled(false);
               router.attach(content.getMatch(),proxy);
            } else {
               // hope the directory resource can handle it
               getLogger().info("  Directory: "+content.getSource()+" at "+content.getMatch());
               Application app = new Application(context) {
                  public Restlet createRoot() {
                     Directory directory = new Directory(getContext(),uri);
                     directory.setIndexName("index.html");
                     return directory;
                  }
               };
               router.attach(content.getMatch(),app);
            }
         } catch (Exception ex) {
            getLogger().log(Level.SEVERE,"Cannot add content at "+content.getMatch(),ex);
         }
      }
      List<Link> metadataSet = hostConf.getLinks().get("metadata");
      Link metadata = metadataSet==null ? null : metadataSet.size()>0 ? metadataSet.get(0) : null;
      List<Link> autoconfList = hostConf.getLinks().get("autoconf");
      this.resourceManager = new ResourceManager(getLogger(),hostConf.getLinks());
      this.context.getAttributes().put(ResourceManager.ATTR, resourceManager);
      // TODO: make the expiration configurable
      client = context.getClientDispatcher();
      if (autoconfList!=null && autoconfList.size()>0) {
         autoConf = autoconfList.get(autoconfList.size()-1);
         applications = new TreeMap<String,AppInfo>();
         this.scriptManager = new ScriptManager(getContext(),client,metadata,autoConf.getUsername(),autoConf.getPassword(),60000);
         this.context.getAttributes().put(ScriptManager.ATTR, scriptManager);
         check();
      } else {
         this.scriptManager = new ScriptManager(getContext(),client,metadata,null,null,60000);
         this.context.getAttributes().put(ScriptManager.ATTR, scriptManager);
         configureStaticApplications();
      }
      
   }

   public Context getContext() {
      return context;
   }
   
   public boolean isStatic() {
      return isStatic;
   }
   
   public Logger getLogger() {
      return context.getLogger();
   }
   
   public Date getEdited() {
      return edited;
   }
   
   public void setEdited(Date edited)
   {
      this.edited = edited;
   }
   
   public VirtualHost getVirtualHost() {
      return vhost;
   }
   
   static String getTerm(Entry entry,URI uterm)
   {
      Term term = entry.getTerm(uterm);
      return term==null ? null : term.getFirstValue();
   }
   
   protected String formatLog(Request request, Response response,int duration) {
      StringBuilder sb = new StringBuilder();
      long currentTime = System.currentTimeMillis();

      // Append the date of the request
      sb.append(String.format("%tF", currentTime));
      sb.append('\t');

      // Append the time of the request
      sb.append(String.format("%tT", currentTime));
      sb.append('\t');

      // Append the client IP address
      String clientAddress = request.getClientInfo().getUpstreamAddress();
      sb.append((clientAddress == null) ? "-" : clientAddress);
      sb.append('\t');

      // Append the user name (via IDENT protocol)
      if ((request.getChallengeResponse() != null) && (request.getChallengeResponse().getIdentifier() != null)) {
         sb.append(request.getChallengeResponse().getIdentifier());
      } else {
         // [enddef]
         sb.append('-');
      }
      sb.append('\t');

      // Append the server IP address
      String serverAddress = response.getServerInfo().getAddress();
      sb.append((serverAddress == null) ? "-" : serverAddress);
      sb.append('\t');

      // Append the server port
      Integer serverport = response.getServerInfo().getPort();
      sb.append((serverport == null) ? "-" : serverport.toString());
      sb.append('\t');

      // Append the method name
      String methodName = (request.getMethod() == null) ? "-" : request.getMethod().getName();
      sb.append((methodName == null) ? "-" : methodName);

      // Append the resource path
      sb.append('\t');
      String resourcePath = (request.getResourceRef() == null) ? "-"
              : request.getResourceRef().getPath();
      sb.append((resourcePath == null) ? "-" : resourcePath);

      // Append the resource query
      sb.append('\t');
      String resourceQuery = (request.getResourceRef() == null) ? "-"
              : request.getResourceRef().getQuery();
      sb.append((resourceQuery == null) ? "-" : resourceQuery);

      // Append the status code
      sb.append('\t');
      sb.append((response.getStatus() == null) ? "-" : Integer.toString(response.getStatus().getCode()));

      // Append the returned size
      sb.append('\t');

      if (!response.isEntityAvailable()
              || Status.REDIRECTION_NOT_MODIFIED.equals(response.getStatus())
              || Status.SUCCESS_NO_CONTENT.equals(response.getStatus())
              || Method.HEAD.equals(request.getMethod())) {
         sb.append('0');
      } else {
         sb.append((response.getEntity().getSize() == -1) ? "-" : Long.toString(response.getEntity().getSize()));
      }

      // Append the received size
      sb.append('\t');

      if (request.getEntity() == null) {
         sb.append('0');
      } else {
         sb.append((request.getEntity().getSize() == -1) ? "-" : Long.toString(request.getEntity().getSize()));
      }

      // Append the duration
      sb.append('\t');
      sb.append(duration);

      // Append the host reference
      sb.append('\t');
      sb.append((request.getHostRef() == null) ? "-" : request.getHostRef().toString());

      // Append the agent name
      sb.append('\t');
      String agentName = request.getClientInfo().getAgent();
      sb.append((agentName == null) ? "-" : agentName);

      // Append the referrer
      sb.append('\t');
      sb.append((request.getReferrerRef() == null) ? "-" : request.getReferrerRef().getIdentifier());

      return sb.toString();
   }

   protected void configureSecurity()
   {
      if (security==null) {
         return;
      }
      // Load the layout locations
      URI protectedTermLoc = URI.create(autoConf.getLink().toString()+T_SECURITY_PROTECTED_PATH.toString());
      getLogger().info("Loading protected paths from "+protectedTermLoc);
      FeedClient layoutFeedClient = new FeedClient(client,new Reference(protectedTermLoc));
      if (autoConf.getUsername()!=null) {
         layoutFeedClient.setIdentity(autoConf.getUsername(),autoConf.getPassword());
      }
      
      try {
         
         FeedBuilder builder = new FeedBuilder();
         Response response = layoutFeedClient.get(builder);
         if (response.getStatus().isSuccess()) {
            Feed feed = builder.getFeed();
            feed.index();
            
            Set<String> names = new TreeSet<String>();

            List<SecurityGuard.SecureRoute> copyOfRoutes = new ArrayList<SecurityGuard.SecureRoute>();
            copyOfRoutes.addAll(security.getRoutes());
            try {
               security.getRoutes().clear();
               for (Entry entry : feed.getEntriesByTerm(T_SECURITY_PROTECTED_PATH)) {
                  Term paths = entry.getTerm(T_SECURITY_PROTECTED_PATH);

                  getLogger().info("Security entry "+entry.getId());
                  String unauthorized = getTerm(entry,T_SECURITY_REDIRECT_UNAUTHORIZED);
                  Reference unauthorizedRef = unauthorized==null ? null : new Reference(unauthorized);
                  String login = getTerm(entry,T_SECURITY_REDIRECT_UNAUTHORIZED);
                  Reference loginRef = login==null ? null : new Reference(login);
                  
                  try {
                     Set<UUID> groups = new TreeSet<UUID>();
                     Term groupSpec = entry.getTerm(T_SECURITY_GROUP_ID);
                     if (groupSpec!=null) {
                        for (String id : groupSpec.getValues()) {
                           UUID uuid = UUID.fromString(id);
                           groups.add(uuid);
                           getLogger().info("... group "+uuid+" required.");
                        }
                     }
                     Set<UUID> roles = new TreeSet<UUID>();
                     Term rolesSpec = entry.getTerm(T_SECURITY_ROLE_ID);
                     if (rolesSpec!=null) {
                        for (String id : rolesSpec.getValues()) {
                           UUID uuid = UUID.fromString(id);
                           roles.add(uuid);
                           getLogger().info("... role "+uuid+" required.");
                        }
                     }
                     Collection<String> values = paths.getValues();
                     if (values!=null) {
                        for (String path : paths.getValues()) {
                           getLogger().info("... adding secure path "+path);
                           SecurityGuard.SecureRoute route = security.addSecureArea(path, loginRef, unauthorizedRef);
                           route.getArea().getRequiredGroups().addAll(groups);
                           route.getArea().getRequiredRoles().addAll(roles);
                        }
                     }
                  } catch (IllegalArgumentException ex) {
                     getLogger().severe("Bad UUID value in entry "+entry.getId()+": "+ex.getMessage());
                  }
               }
            } catch (Exception ex) {
               getLogger().log(Level.SEVERE,"Exception while processing protected path feed.  Restoring secure routes.",ex);
               security.getRoutes().clear();
               security.getRoutes().addAll(copyOfRoutes);
            }
         }
      } catch (Exception ex) {
         getLogger().log(Level.SEVERE,"Cannot get feed "+protectedTermLoc+" due to exception: "+ex.getMessage(),ex);
      }
   }
   
   protected void configureLayouts() {
      // Load layouts
      URI layoutTemLoc = URI.create(autoConf.getLink().toString()+T_LAYOUT.toString());
      getLogger().info("Loading Layouts from "+layoutTemLoc);
      FeedClient appFeedClient = new FeedClient(client,new Reference(layoutTemLoc));
      if (autoConf.getUsername()!=null) {
         appFeedClient.setIdentity(autoConf.getUsername(),autoConf.getPassword());
      }
      try {
         FeedBuilder builder = new FeedBuilder();
         Response response = appFeedClient.get(builder);
         boolean found = false;
         if (!response.getStatus().isSuccess()) {
            if (response.getStatus().getCode()!=404) {
               getLogger().severe("Cannot get feed "+layoutTemLoc+", status="+response.getStatus().getCode());
            } else {
               getLogger().warning("No application feed to load.");
            }
         } else {
            Feed feed = builder.getFeed();
            feed.index();
            Set<String> ids = new TreeSet<String>();
            String base = T_BASE.toString();
            for (Entry entry : feed.getEntriesByTerm(T_LAYOUT)) {
               String id = entry.getId();
               ids.add(id);
               Text text = entry.getContent();
               URI location = text.getElement().getBaseURI().resolve(text.getSourceLink());
               ScriptManager.Entry info = scriptManager.get(id);
               if (info!=null && entry.getEdited().equals(info.getEdited())) {
                  getLogger().info("Script entry "+id+", location="+location+" not modified.");
                  continue;
               }
               Set<Term> criteria = new TreeSet<Term>();
               for (Term term : entry.getTerms().values()) {
                  if (!term.getURI().toString().startsWith(base)) {
                     criteria.add(term);
                  }
               }
               Term match = entry.getTerm(T_LAYOUT_MATCH);
               Term mediaTypeT = entry.getTerm(T_LAYOUT_MEDIA_TYPE);
               Term priorityT = entry.getTerm(T_LAYOUT_PRIORITY);
               MediaType mediaType = mediaTypeT==null ? MediaType.APPLICATION_XHTML_XML : MediaType.valueOf(mediaTypeT.getFirstValue());
               String matchValue = match==null ? null : match.getFirstValue();
               if (matchValue==null && match!=null) {
                  // set the value to the empty string
                  matchValue = "";
               }
               int priority = priorityT==null ? -1 : Integer.parseInt(priorityT.getFirstValue());
               if (info!=null) {
                  getLogger().info("Reloading script entry "+id+", location="+location);
                  scriptManager.reload(id,entry.getEdited(),criteria,matchValue,priority,mediaType);
               } else {
                  getLogger().info("Configuring script entry "+id+", location="+location);
                  scriptManager.add(id,entry.getEdited(),criteria,matchValue,priority,mediaType,location);
               }
            }
            for (String id : scriptManager.getKeys()) {
               if (!ids.contains(id)) {
                  getLogger().info("Removing script entry "+id);
                  scriptManager.remove(id);
               }
            }
         }
      } catch (Exception ex) {
         getLogger().log(Level.SEVERE,"Cannot get feed "+layoutTemLoc+" due to exception: "+ex.getMessage(),ex);
      }
   }
   
   protected void configureResources() {
      // Load layouts
      URI resourceTermLoc = URI.create(autoConf.getLink().toString()+T_RESOURCE.toString());
      getLogger().info("Loading resources from "+resourceTermLoc);
      FeedClient appFeedClient = new FeedClient(client,new Reference(resourceTermLoc));
      if (autoConf.getUsername()!=null) {
         appFeedClient.setIdentity(autoConf.getUsername(),autoConf.getPassword());
      }
      try {
         FeedBuilder builder = new FeedBuilder();
         Response response = appFeedClient.get(builder);
         boolean found = false;
         if (!response.getStatus().isSuccess()) {
            if (response.getStatus().getCode()!=404) {
               getLogger().severe("Cannot get feed "+resourceTermLoc+", status="+response.getStatus().getCode());
            } else {
               getLogger().warning("No application feed to load.");
            }
         } else {
            Feed feed = builder.getFeed();
            feed.index();
            Set<String> ids = new TreeSet<String>();
            String base = T_BASE.toString();
            for (Entry entry : feed.getEntriesByTerm(T_RESOURCE)) {
               String id = entry.getId();
               ids.add(id);
               ResourceManager.Entry info = resourceManager.get(id);
               Term nameT = entry.getTerm(T_RESOURCE_NAME);
               Term pathT = entry.getTerm(T_RESOURCE_PATH);
               Term relationT = entry.getTerm(T_RESOURCE_RELATION);
               Term queryT = entry.getTerm(T_RESOURCE_QUERY);
               Term mediaTypeT = entry.getTerm(T_RESOURCE_MEDIA_TYPE);
               if (nameT==null || nameT.getFirstValue()==null) {
                  getLogger().warning("Ignoring resource "+id+" without a name.");
                  continue;
               }
               String name = nameT.getFirstValue();
               if (info!=null && entry.getEdited().equals(info.getEdited())) {
                  getLogger().info("Resource entry "+id+" named "+name+" not modified.");
                  continue;
               }
               String query = null;
               if (queryT==null || queryT.getFirstValue()==null) {
                  Text text = entry.getContent();
                  if (text.getSourceLink()!=null) {
                     URI location = text.getElement().getBaseURI().resolve(text.getSourceLink());
                  }
               } else {
                  query = queryT.getFirstValue();
               }
               MediaType mediaType = mediaTypeT==null ? null : MediaType.valueOf(mediaTypeT.getFirstValue());
               if (info!=null) {
                  getLogger().info("Reloading resource entry "+id+" named "+name);
                  resourceManager.reload(id,entry.getEdited(),name,pathT==null ? null : pathT.getFirstValue(),relationT==null ? null : relationT.getFirstValue(),mediaType,query);
               } else {
                  getLogger().info("Configuring resource entry "+id+" named "+name);
                  resourceManager.add(id,entry.getEdited(),name,pathT==null ? null : pathT.getFirstValue(),relationT==null ? null : relationT.getFirstValue(),mediaType,query);
               }
            }
            for (String id : resourceManager.getKeys()) {
               if (!ids.contains(id)) {
                  getLogger().info("Removing resource entry "+id);
                  resourceManager.remove(id);
               }
            }
         }
      } catch (Exception ex) {
         getLogger().log(Level.SEVERE,"Cannot get feed "+resourceTermLoc+" due to exception: "+ex.getMessage(),ex);
      }
   }
   
   
   protected void configureStaticApplications() {
      String internalName = hostConf.getInternalName();
      if (staticApplications.size()>0) {
         for (AppInfo info : staticApplications) {
            router.detach(info.app);
            router.attach(info.match,info.app);
            if (internalName!=null) {
               internalRouter.detach(info.app);
               String route = "/"+internalName+info.match;
               getLogger().info("   updating internal route: "+route);
               internalRouter.attach(route,info.app).getTemplate().setMatchingMode(Template.MODE_STARTS_WITH);
            }
         }
      } else {
         for (Configuration.AppConf appConf : hostConf.getApplications()) {
            try {
               getLogger().info("  Application: "+appConf.getApplicationDef().getName()+" at "+appConf.getMatch());
               LinkSet set = new LinkSet();
               set.addLinkSet(hostConf.getLinks());
               set.addLinkSet(appConf.getLinks());
               Application app = appConf.getApplication(this.context,set);
               router.attach(appConf.getMatch(),app);
               if (internalName!=null) {
                  String route = "/"+internalName+appConf.getMatch();
                  getLogger().info("   adding internal route: "+route);
                  internalRouter.attach(route,app).getTemplate().setMatchingMode(Template.MODE_STARTS_WITH);
               }
               AppInfo info = new AppInfo(app,null);
               info.match = appConf.getMatch();
               staticApplications.add(info);
            } catch (Exception ex) {
               getLogger().log(Level.SEVERE,"Cannot start application at "+appConf.getMatch(),ex);
            }
         }
      }
   }
   
   protected void configureApplications() {
      // Load applications
      URI appTermLoc = URI.create(autoConf.getLink().toString()+T_APP.toString());
      getLogger().info("Loading Applications from "+appTermLoc);
      FeedClient appFeedClient = new FeedClient(client,new Reference(appTermLoc));
      if (autoConf.getUsername()!=null) {
         appFeedClient.setIdentity(autoConf.getUsername(),autoConf.getPassword());
      }
      try {
         FeedBuilder builder = new FeedBuilder();
         Response response = appFeedClient.get(builder);
         boolean found = false;
         if (!response.getStatus().isSuccess()) {
            if (response.getStatus().getCode()!=404) {
               getLogger().severe("Cannot get feed "+appTermLoc+", status="+response.getStatus().getCode());
            } else {
               getLogger().warning("No application feed to load.");
            }
         } else {
            Feed feed = builder.getFeed();
            feed.index();
            Set<String> ids = new TreeSet<String>();
            List<Entry> entries = new ArrayList<Entry>();
            entries.addAll(feed.getEntriesByTerm(T_APP));
            Comparator<Entry> sorter = new Comparator<Entry>() {
               public int compare(Entry A, Entry B) {
                  Term AP = A.getTerm(T_APP_PRIORITY);
                  Term BP = B.getTerm(T_APP_PRIORITY);
                  //getLogger().info("A: "+A.getTitle()+" "+AP+" vs B: "+B.getTitle()+" "+BP);
                  if (BP==null && AP==null) {
                     return 0;
                  } else if (AP!=null && BP==null) {
                     return -1;
                  } else if (AP==null && BP!=null) {
                     return 1;
                  } else {
                     int a = Integer.parseInt(AP.getFirstValue());
                     int b = Integer.parseInt(BP.getFirstValue());
                     return a>b ? -1 : a==b ? 0 : 1;
                  }
               }
               public boolean equals(Object obj) {
                  return obj==this;
               }
            };
            Collections.sort(entries,sorter);
            Collections.reverse(entries);
            for (Entry entry : entries) {
               found = true;
               URI baseURI = entry.getDocument().getBaseURI();
               Term priority = entry.getTerm(T_APP_PRIORITY);
               getLogger().info("Application: title: "+entry.getTitle()+", base: "+baseURI+", priority: "+(priority==null ? "" : priority.getFirstValue()));
               ids.add(entry.getId());
               
               boolean exact = false;
               if (entry.getTerm(T_APP_MATCH_MODE)!=null) {
                  exact = "exact".equals(entry.getTerm(T_APP_MATCH_MODE).getFirstValue());
               }
               
               AppInfo appInfo = applications.get(entry.getId());
               if (appInfo!=null && appInfo.edited.getTime()>=entry.getEdited().getTime()) {
                  // reattach to make sure it is in the right order
                  router.detach(appInfo.app);
                  Term match = entry.getTerm(T_APP_MATCH);
                  if (match!=null && match.getValues()!=null) {
                     for (String pattern : match.getValues()) {
                        TemplateRoute route = router.attach(pattern,appInfo.app);
                        if (exact) {
                           route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                        }
                     }
                  } else {
                     TemplateRoute route = router.attach("",appInfo.app);
                     if (exact) {
                        route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                     }
                  }
                  getLogger().info("No changes, skipping.");
                  continue;
               }
               for (Term t : entry.getTerms().values()) {
                  getLogger().info(t.getURI()+": "+t.getFirstValue());
               }
               getLogger().info(T_APP_MATCH.toString());
               Term classTerm = entry.getTerm(T_APP_CLASS);
               Term proxyTerm = entry.getTerm(T_APP_PROXY);
               Context appContext = context.createChildContext();
               LinkSet set = new LinkSet();
               set.addLinkSet(entry.getLinks());
               set.addLinkSet(hostConf.getLinks());
               appContext.getAttributes().put(WebComponent.LINKS_ATTR,set);
               appContext.getAttributes().put(ScriptManager.ATTR,scriptManager);
               appContext.getAttributes().put(ResourceManager.ATTR,resourceManager);
               for (URI t : entry.getTerms().keySet()) {
                  String value = entry.getTerm(t).getFirstValue();
                  getLogger().info("Setting parameter: "+t+"="+value);
                  appContext.getParameters().set(t.toString(),value,false);
               }
               appContext.getParameters().set("username",autoConf.getUsername(),false);
               appContext.getParameters().set("password",autoConf.getPassword(),false);
               for (Term t : entry.getTerms().values()) {
                  String key = t.getURI().toString();
                  if (t.getValues()!=null) {
                     for (String value : t.getValues()) {
                        appContext.getParameters().add(key, value);
                     }
                  } else {
                     appContext.getParameters().add(key,"true");
                  }
               }
               Application app = null;
               if (proxyTerm!=null) {
                  String value = proxyTerm.getFirstValue();
                  List<Link> links = entry.getLinks().get(value);
                  Link target = null;
                  if (links!=null && links.size()>0) {
                     target = links.get(0);
                  }
                  if (target==null) {
                     links = hostConf.getLinks().get(value);
                     if (links!=null && links.size()>0) {
                        target = links.get(0);
                     }
                  }
                  if (target!=null) {
                     app = new ProxyApplication(appContext,target);
                  }
               } else if (classTerm!=null) {
                  String className = classTerm.getFirstValue();
                  List<Link> libraryLinks = entry.getLinks().get("library");
                  String href = null;
                  Text text = entry.getContent();
                  if (text!=null) {
                     href = text.getSourceLink();
                  }
                  Class<Application> appClass = null;
                  Class<?> foundClass = null;
                  if ((libraryLinks==null || libraryLinks.size()==0) && href==null) {
                     foundClass = Class.forName(className);
                  } else {
                     URL [] downloads = new URL[(libraryLinks==null ? 0 : libraryLinks.size())+(href==null ? 0 : 1)];
                     if (href!=null) {
                        downloads[0] = entry.getDocument().getDocumentElement().getBaseURI().resolve(href).toURL();
                     }
                     if (libraryLinks!=null) {
                        for (int pos = href==null ? 0 : 1; pos<libraryLinks.size(); pos++) {
                           Link link = libraryLinks.get(pos);
                           downloads[pos] = link.getLink().toURL();
                        }
                     }
                     
                     URL [] urls = new URL[downloads.length];
                     for (int i=0; i<downloads.length; i++) {
                        File jarfile = File.createTempFile("jar-T"+System.currentTimeMillis()+"-", ".jar");
                        urls[i] = jarfile.toURL();
                        URLRetriever retriever = new URLRetriever(downloads[i]);
                        try {
                           retriever.retrieve(jarfile, autoConf.getUsername(), autoConf.getPassword());
                        } catch (IOException ex) {
                           getLogger().info("Cannot download "+downloads[i]+" due to: "+ex.getMessage());
                           continue;
                        }
                     }
                     URLClassLoader classLoader = new URLClassLoader(urls,ConfiguredHost.class.getClassLoader()) {
                        protected PermissionCollection getPermissions(CodeSource source) {
                           PermissionCollection collection = super.getPermissions(source);
                           URL url = source.getLocation();
                           getLogger().info("Code source: "+url);
                           Enumeration<Permission> permissions = collection.elements();
                           while (permissions.hasMoreElements()) {
                              Permission p = permissions.nextElement();
                              getLogger().info("Permission: "+p.getName()+", "+p.getClass().getName()+", action: "+p.getActions());
                           }
                           return collection;
                        } 
                     };
                     foundClass = classLoader.loadClass(className);
                     PermissionCollection collection = foundClass.getProtectionDomain().getPermissions();
                     Enumeration<Permission> permissions = collection.elements();
                     while (permissions.hasMoreElements()) {
                        Permission p = permissions.nextElement();
                        getLogger().info("Permission: "+p.getName()+", "+p.getClass().getName()+", action: "+p.getActions());
                     }
                  }
                  if (!Application.class.isAssignableFrom(foundClass)) {
                     getLogger().info("Class "+className+" is not an subclas of "+Application.class.getName());
                     continue;
                  }
                  appClass = (Class<Application>)foundClass;
                  Constructor<Application> makeit = appClass.getConstructor(Context.class);
                  app = makeit.newInstance(appContext);
               }
               if (app!=null) {
                  if (appInfo!=null) {
                     router.detach(appInfo.app);
                     appInfo.app = app;
                     appInfo.edited = entry.getEdited();
                     Term match = entry.getTerm(T_APP_MATCH);
                     if (match!=null && match.getValues()!=null) {
                        for (String pattern : match.getValues()) {
                           TemplateRoute route = router.attach(pattern,app);
                           if (exact) {
                              route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                           }
                        }
                     } else {
                        TemplateRoute route = router.attach("",app);
                        if (exact) {
                           route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                        }
                     }
                  } else {
                     applications.put(entry.getId(),new AppInfo(app,entry.getEdited()));
                     Term match = entry.getTerm(T_APP_MATCH);
                     if (match!=null && match.getValues()!=null) {
                        for (String pattern : match.getValues()) {
                           TemplateRoute route = router.attach(pattern,app);
                           if (exact) {
                              route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                           }
                        }
                     } else {
                        TemplateRoute route = router.attach("",app);
                        if (exact) {
                           route.getTemplate().setMatchingMode(Template.MODE_EQUALS);
                        }
                     }
                  }
               }

            }
            Set<String> currentIds = new TreeSet<String>();
            currentIds.addAll(applications.keySet());
            for (String id : currentIds) {
               if (!ids.contains(id)) {
                  AppInfo appInfo = applications.remove(id);
                  router.detach(appInfo.app);
               }
            }
         }
         if (!found) {
            getLogger().warning("No application was found.");
         } else {
            AppInfo appInfo = applications.remove("<none>");
            if (appInfo!=null) {
               router.detach(appInfo.app);
            }
         }
      } catch (Exception ex) {
         getLogger().log(Level.SEVERE,"Cannot get feed "+appTermLoc+" due to exception: "+ex.getMessage(),ex);
      }
      
   }

   public void setupLog() {
      if (hostLog==null) {
         return;
      }
      String pattern = hostConf.getLogConfiguration().get("pattern");
      String value = hostConf.getLogConfiguration().get("limit");
      int limit = 10*1024*1024;
      if (value!=null) {
         try {
            limit = Integer.parseInt(value);
         } catch (NumberFormatException ex) {
            getLogger().log(Level.SEVERE,"Cannot parse limit value "+value+" for log configuration: "+ex.getMessage());
         }
      }
      value = hostConf.getLogConfiguration().get("count");
      int count = 100;
      if (value!=null) {
         try {
            count = Integer.parseInt(value);
         } catch (NumberFormatException ex) {
            getLogger().log(Level.SEVERE,"Cannot parse count value "+value+" for log configuration: "+ex.getMessage());
         }
      }
      boolean append = true;
      value = hostConf.getLogConfiguration().get("append");
      if (value!=null) {
         append = value.equals("true");
      }
      try {
         AccessLogFileHandler handler = new AccessLogFileHandler(pattern,limit,count,append);
         value = hostConf.getLogConfiguration().get("encoding");
         if (value!=null) {
            handler.setEncoding(value);
         }
         value = hostConf.getLogConfiguration().get("level");
         handler.setLevel(Level.ALL);
         if (value!=null) {
            try {
               handler.setLevel(Level.parse(value));
            } catch (Exception ex) {
              getLogger().log(Level.SEVERE,"Cannot parse level value "+value+" for log configuration: "+ex.getMessage());
            }
         }
         hostLog.addHandler(handler);
      } catch (IOException ex) {
         getLogger().log(Level.SEVERE,"Cannot instantiate access log file handler for host.",ex);
      }
   }
   
   public synchronized void check() {
      if (autoConf==null) {
         return;
      }
      configureSecurity();
      configureApplications();
      configureResources();
      configureStaticApplications();
      configureLayouts();
   }

   
}
